package com.tencent.cloud.iov.processor.action;

import com.tencent.cloud.iov.action.Action;
import com.tencent.cloud.iov.processor.utils.LogHelper;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;

/**
 * 行为方法查找类
 *
 * 匹配所有添加 @Action 注解的方法及其类信息
 *
 * @author showxiao
 * @date 2018/4/18
 */

class ActionMethodCollector {

    private final ProcessingEnvironment mProcessingEnv;
    private final LogHelper mLogHelper;

    ActionMethodCollector(ProcessingEnvironment processingEnv) {
        mProcessingEnv = processingEnv;
        mLogHelper = new LogHelper(processingEnv);
    }

    Map<TypeElement, List<ExecutableElement>> collect(RoundEnvironment roundEnv) {
        Map<TypeElement, List<ExecutableElement>> map = new HashMap<>();
        Collection<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Action.class);
        collectActionMethods(map, elements, null);
        return map;
    }

    private void collectActionMethods(Map<TypeElement, List<ExecutableElement>> map,
                                      Collection<? extends Element> elements,
                                      TypeElement classElement) {

        boolean isCollectingSuperMethods = classElement != null;

        for (Element element : elements) {
            final ExecutableElement methodElement = getMethodElement(element);
            if (methodElement == null) {
                continue;
            }

            if (classElement == null) {
                classElement = (TypeElement) methodElement.getEnclosingElement();
                if (classElement.getKind() != ElementKind.CLASS
                        && classElement.getKind() != ElementKind.INTERFACE) {
                    classElement = null;
                    continue;
                }
            }

            // 查找类对应方法集合
            List<ExecutableElement> methodElements = map.get(classElement);
            if (methodElements == null) {
                methodElements = new ArrayList<>();
                map.put(classElement, methodElements);
            }
            // 添加至方法集合：跳过重写方法，避免重复的判断逻辑
            if (!isElementOverridden(methodElement, methodElements, classElement)) {
                methodElements.add(methodElement);
            }

            // 遍历父类
            collectSuperClassMethods(map, methodElement, classElement);
            // 遍历实现接口
            collectInterfacesMethods(map, methodElement, classElement);

            // 即将收集下一个类/接口的方法集合，重置类信息
            if (!isCollectingSuperMethods) {
                classElement = null;
            }
        }
    }

    /**
     * 获取方法元素
     *
     * @param element
     * @return
     */
    private ExecutableElement getMethodElement(Element element) {
        if (element.getAnnotation(Action.class) == null) {
            return null;
        }

        if (element.getKind() != ElementKind.METHOD) {
            mLogHelper.error(element, "Action annotation should be used for methods only");
            return null;
        }

        return (ExecutableElement) element;
    }

    /**
     * 判断当前方法与集合中已有方法（相互）重写
     *
     * @param methodElement
     * @param methodElements
     * @param classElement
     * @return
     */
    private boolean isElementOverridden(ExecutableElement methodElement,
                                        List<ExecutableElement> methodElements,
                                        TypeElement classElement) {
        Elements elementUtils = mProcessingEnv.getElementUtils();
        for (ExecutableElement lastElement : methodElements) {
            if (methodElement == lastElement
                    || elementUtils.overrides(lastElement, methodElement, classElement)
                    || elementUtils.overrides(methodElement, lastElement, classElement)) {
                return true;
            }

            String curValue = methodElement.getAnnotation(Action.class).value();
            String lastValue = lastElement.getAnnotation(Action.class).value();
            if (curValue.equals(lastValue)) {
                mLogHelper.error(lastElement, "Duplicate action id: " + curValue);
                return false;
            }
        }

        return false;
    }

    /**
     * 遍历父类行为方法
     *
     * @param map
     * @param methodElement
     * @param classElement
     */
    private void collectSuperClassMethods(Map<TypeElement, List<ExecutableElement>> map,
                                          ExecutableElement methodElement,
                                          TypeElement classElement) {
        TypeMirror superClass =
                ((TypeElement) methodElement.getEnclosingElement()).getSuperclass();
        if (superClass instanceof DeclaredType) {
            TypeElement typeElement = (TypeElement) ((DeclaredType) superClass).asElement();
            List<? extends Element> enclosedElements = typeElement.getEnclosedElements();
            collectActionMethods(map, enclosedElements, classElement);
        }
    }

    /**
     * 遍历接口行为方法
     *
     * @param map
     * @param methodElement
     * @param classElement
     */
    private void collectInterfacesMethods(Map<TypeElement, List<ExecutableElement>> map,
                                          ExecutableElement methodElement,
                                          TypeElement classElement) {
        List<? extends TypeMirror> interfaces =
                ((TypeElement) methodElement.getEnclosingElement()).getInterfaces();
        for (TypeMirror typeMirror : interfaces) {
            if (typeMirror instanceof DeclaredType) {
                TypeElement typeElement = (TypeElement) ((DeclaredType) typeMirror).asElement();
                List<? extends Element> enclosedElements = typeElement.getEnclosedElements();
                collectActionMethods(map, enclosedElements, classElement);
            }
        }
    }

}
